package com.thinkingcoder.tcrpc.core.governance;

import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@ToString
public class SlidingTimeWindow {

    public static final int DEFAULT_SIZE = 30;

    @Getter
    private final int size;
    @Getter
    private final RingBuffer ringBuffer;
    @Getter
    private int sum;

    private int startMark = -1;
    private int prevMark = -1;
    @Getter
    private int currMark = -1;

    @Getter
    private long startTimestamp = -1L;
    private long prevTimestamp = -1L;
    @Getter
    private long currTimestamp = -1L;

    public SlidingTimeWindow() {
        this(DEFAULT_SIZE);
    }

    public SlidingTimeWindow(int size) {
        this.size = size;
        this.ringBuffer = new RingBuffer(this.size);
    }

    /**
     * record current timestamp millis.
     * 
     * @param millis 调用时间的时间戳
     */
    public synchronized void record(long millis) {
        log.debug("window before : " + this.toString());
        log.debug("window.record( " + millis + " )");
        long timestamp = millis / 1000;
        if (startTimestamp == -1L) {
            initRing(timestamp);
        } else { // TODO prev 是否需要考虑
            if (timestamp == currTimestamp) {
                log.debug(
                    "window timestamp : " + timestamp + " , currTimestamp : " + currTimestamp + " , size : " + size);
                this.ringBuffer.increase(currMark, 1);
            } else if (timestamp > currTimestamp && timestamp < currTimestamp + size) {
                int offset = (int)(timestamp - currTimestamp);
                log.debug("window timestamp : " + timestamp + " , currentTimestamp : " + currTimestamp + " , size : "
                    + size + " , offset : " + offset);
                this.ringBuffer.reset(currMark + 1, offset);
                this.ringBuffer.increase(currMark + offset, 1);
                currTimestamp = timestamp;
                currMark = (currMark + offset) % size;
            } else if (timestamp >= currTimestamp + size) {
                log.debug(
                    "window timestamp : " + timestamp + " , currentTimestamp :" + currTimestamp + " , size : " + size);
                this.ringBuffer.reset();
                initRing(timestamp);
            }
        }
        this.sum = this.ringBuffer.sum();
        log.debug("window after : " + this.toString());
    }

    public int calcSum() {
        long timestamp = System.currentTimeMillis() / 1000;
        if (timestamp > currTimestamp && timestamp < currTimestamp + size) {
            int offset = (int)(timestamp - currTimestamp);
            log.debug("calc sum for window timestamp:" + timestamp + ", currentTimestamp:" + currTimestamp + ", size:"
                + size + ", offset:" + offset);
            this.ringBuffer.reset(currMark + 1, offset);
            currTimestamp = timestamp;
            currMark = (currMark + offset) % size;
        } else if (timestamp >= currTimestamp + size) {
            log.debug("calc sum for window timestamp:" + timestamp + ", currentTimestamp:" + currTimestamp + ", size:"
                + size);
            this.ringBuffer.reset();
            initRing(timestamp);
        }
        log.debug("calc sum for window:" + this);
        return ringBuffer.sum();
    }

    private void initRing(long timestamp) {
        log.debug("window initRing timestamp : " + timestamp);
        this.startTimestamp = timestamp;
        this.currTimestamp = timestamp;
        this.currMark = 0;
        this.ringBuffer.increase(0, 1);
    }

}
